package lexer

import (
	"fuck/logger"
	"fuck/token"
	"strings"
)

type Lexer struct {
	input       string
	nowChar     byte
	nextPointer int
	lineNum     int
}

func CreateLexer(input string) *Lexer {
	l := &Lexer{input, 0, 0, 1}
	l.readChar()
	return l
}

func (l *Lexer) NextToken() token.Token {
	//每次调用时，nowChar即为当前要处理的字符，nextPointer为nowChar的后一个字符的索引
	//该函数返回时，nowChar变为下一次要处理的字符，nextPointer同理
	defer l.readChar()
	l.eatWhitespace()
	ch := l.nowChar
	var tok token.Token
	switch ch {
	case 0:
		return token.Token{Type: token.EOF, LineNum: l.lineNum}
	case '=':
		if l.peekChar() == '=' {
			l.readChar()
			tok = CreateToken(token.EQ, "==", l.lineNum)
		} else {
			tok = CreateToken(token.ASSIGN, "=", l.lineNum)
		}
	case '!':
		if l.peekChar() == '=' {
			l.readChar()
			tok = CreateToken(token.NE, "!=", l.lineNum)
		} else {
			tok = CreateToken(token.BANG, "!", l.lineNum)
		}
	case '+':
		tok = CreateToken(token.PLUS, "+", l.lineNum)
	case '-':
		tok = CreateToken(token.MINUS, "-", l.lineNum)
	case '*':
		tok = CreateToken(token.ASTERISK, "*", l.lineNum)
	case '/':
		tok = CreateToken(token.SLASH, "/", l.lineNum)
	case '%':
		tok = CreateToken(token.MOD, "%", l.lineNum)
	case ',':
		tok = CreateToken(token.COMMA, ",", l.lineNum)
	case ';':
		tok = CreateToken(token.SEMICOLON, ";", l.lineNum)
	case '(':
		tok = CreateToken(token.LPAREN, "(", l.lineNum)
	case ')':
		tok = CreateToken(token.RPAREN, ")", l.lineNum)
	case '{':
		tok = CreateToken(token.LBRACE, "{", l.lineNum)
	case '}':
		tok = CreateToken(token.RBRACE, "}", l.lineNum)
	case '<':
		tok = CreateToken(token.LT, "<", l.lineNum)
	case '>':
		tok = CreateToken(token.GT, ">", l.lineNum)
	case '"':
		tok = CreateToken(token.STRING, l.readString(), l.lineNum)
	case '[':
		tok = CreateToken(token.LBRACKET, "[", l.lineNum)
	case ']':
		tok = CreateToken(token.RBRACKET, "]", l.lineNum)
	case ':':
		tok = CreateToken(token.COLON, ":", l.lineNum)
	default:
		if isIdentFirstChar(ch) {
			start := l.nextPointer - 1
			for isIdentChar(l.peekChar()) {
				l.readChar()
			}
			ident := l.input[start:l.nextPointer]
			if keyType, ok := token.KeywordsType[ident]; ok {
				tok = CreateToken(keyType, ident, l.lineNum)
				break
			}
			tok = CreateToken(token.IDENT, ident, l.lineNum)
			break
		}
		if isNumberChar(ch) {
			start := l.nextPointer - 1
			for isNumberChar(l.peekChar()) {
				l.readChar()
			}
			tok = CreateToken(token.INT, l.input[start:l.nextPointer], l.lineNum)
			break
		}
		logger.Warningf("Invalid char \"%c\" on line %d\n", ch, l.lineNum)
		tok = CreateToken(token.ILLEGAL, string(ch), l.lineNum)
	}
	return tok
}

func CreateToken(tokenType string, str string, lineNum int) token.Token {
	return token.Token{Type: token.Type(tokenType), Literal: string(str), LineNum: lineNum}
}

func (l *Lexer) peekChar() byte {
	if l.nextPointer == len(l.input) {
		return 0
	}
	return l.input[l.nextPointer]
}

func (l *Lexer) readChar() bool { //nowChar改为nextPointer指向的目标，nextPointer+1（除非=数组长度）
	if l.nextPointer == len(l.input) {
		l.nowChar = 0
		return false
	}
	l.nowChar = l.input[l.nextPointer]
	l.nextPointer += 1
	return true
}

var EscapeChars = map[byte]byte{
	'0': 0, 'a': '\a', 'b': '\b', 't': '\t', 'n': '\n', 'r': '\r', '"': '"', '\\': '\\',
}

func (l *Lexer) readString() string {
	pos := l.nextPointer
	for {
		l.readChar()
		if l.nowChar == '"' {
			break
		}

		if l.nowChar == 0 {
			logger.Errorf("(Fatal) Cannot find another  (\")\n ")
			return ""
		}
	}
	return strings.NewReplacer("\\0", string(uint8(0)), "\\a", "\a", "\\b", "\b", "\\t", "\t", "\\n", "\n", "\\r", "\r", "\\\"", "\"", "\\\\", "\\").
		Replace(l.input[pos : l.nextPointer-1])
}

func (l *Lexer) eatWhitespace() {
	for l.nowChar == ' ' || l.nowChar == '\t' || l.nowChar == '\n' || l.nowChar == '\r' {
		if l.nowChar == '\n' {
			l.lineNum += 1
		}
		l.readChar()
	}
}

func isIdentFirstChar(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}

func isIdentChar(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || '0' <= ch && ch <= '9'
}

func isNumberChar(ch byte) bool {
	return '0' <= ch && ch <= '9' || ch == '.'
}
